# -*- coding: utf-8 -*-
"""
Created on Tue Jul  9 09:26:24 2019

@author: MS
"""
import numpy as np

def AnalysisData(X,DataType=None):
    # 分析数据
    # DataType 返回每个属性的数据类型，若为字符串类型，说明是离散型，并统计各个可能取值
    # X1 返回数字化的数据，将离散型数值转变为相应取值的编号
    m=len(X)     #样本数
    n=len(X[0])  #属性数
    if DataType==None: #如果未从外部输入DataType,则需自动创建
        DataType=[]
        for p in range(n):
            DataType.append([type(X[0][p])])
            if DataType[p][0]==str:
                for q in range(m):
                    if X[q][p] not in DataType[p]:
                        DataType[p].append(X[q][p])
    X1=[]   #数字化的数据
    for p in range(m):
        temp=[]
        for q in range(n):
            if DataType[q][0]==str:
                temp.append(DataType[q].index(X[p][q]))
            else:
                temp.append(X[p][q])
        X1.append(temp)
    return DataType,X1

def OneHot(X,DataType,order):
    # one-hot独热编码，将原数据X变为独热编码的数据X1
    if order==None:
        order=[True]*len(DataType)
    num=[len(i)-1 for i in DataType]  #每个属性的取值数
    expand=[num[i]*(not order[i])+order[i] for i in range(len(num))] #每个属性需要扩展的数目
    X1=[]
    for i in range(len(num)):
        if expand[i]==1:
            X1=np.r_[X1,X[:,i]]
        else:
            for n in range(expand[i]):
                X1=np.r_[X1,(X[:,i]==(n+1))]
    return X1.reshape(sum(expand),-1).T

def sigmoid(X):
    return 1/(1+np.exp(-X))

def E(X,Y,para):
    # 计算当前参数下的累积误差
    V,W,gama,theta=para
    m,d=X.shape
    b=sigmoid(np.dot(X,V)-np.dot(np.ones([m,1]),gama))
    Ypre=sigmoid(np.dot(b,W)-np.dot(np.ones([m,1]),theta))
    return np.linalg.norm(Ypre-Y)**2/2/m

def predict(X,para):
    # 通过神经网络模型进行预测
    V,W,gama,theta=para
    m,d=X.shape
    b=sigmoid(np.dot(X,V)-np.dot(np.ones([m,1]),gama))        # 隐藏层输出
    Ypre=sigmoid(np.dot(b,W)-np.dot(np.ones([m,1]),theta))  # 输出层输出
    return Ypre

def initialpara(d,q,l):
    # 初始化参数
    V=np.random.randn(d,q)     # “输入层-隐藏层”连接权重, 产生正则分布的随机数
    W=np.random.randn(q,l)     # “隐藏层-输出层”连接权重
    gama=np.random.randn(1,q)  # 隐藏层阈值
    theta=np.random.randn(1,l) #输出层阈值
    return (V,W,gama,theta)

def stdBP(X,Y,q,para0=None,alpha=0.1,iteration=10):
    # 标准BP算法
    # 数据X，Y均为np.array类型
    # q-----------隐层神经元数
    # alpha-------学习率
    # iteration---迭代次数
    #=======================================
    m,d=X.shape   #m为样本数，d为特征数
    m,l=Y.shape   #l为输出向量维数
    #===============初始化参数===============
    if para0==None:
        V=np.random.randn(d,q)     # “输入层-隐藏层”连接权重, 产生正则分布的随机数
        W=np.random.randn(q,l)     # “隐藏层-输出层”连接权重
        gama=np.random.randn(1,q)  # 隐藏层阈值
        theta=np.random.randn(1,l) #输出层阈值
    else:
        # 这里要注意，之所以如此繁琐的copy操作，是因为若不如此，
        # V,W,gama,theta各参数的变化也会引起输入变量para0的变化
        V0,W0,gama0,theta0=para0
        V=V0.copy()
        W=W0.copy()
        gama=gama0.copy()
        theta=theta0.copy()
    #==============训练神经网络==============
    erro=[]
    for i in range(iteration):
        k=np.random.randint(m)  # 随机挑选一个样本
        xk=X[[k],:]
        #--------前向传播--------
        b=sigmoid(np.dot(xk,V)-gama)      # 隐藏层输出
        ypre=sigmoid(np.dot(b,W)-theta)  # 输出层输出
        #--------后向传播--------
        g=ypre*(1-ypre)*(Y[k,:]-ypre)
        e=b*(1-b)*(np.dot(g,W.T))
        #--------更新参数--------
        W+=alpha*np.dot(b.T,g)
        V+=alpha*np.dot(xk.T,e)
        theta-=alpha*g
        gama-=alpha*e
        #---计算当前累积均方误差--
        erro.append(E(X,Y,(V,W,gama,theta)))
    return (V,W,gama,theta),erro
    
def accBP(X,Y,q,para0=None,alpha=0.1,iteration=10):
    # 累积BP算法
    # 数据X，Y均为np.array类型
    # q-----------隐层神经元数
    # alpha-------学习率
    # iteration---迭代次数
    #=======================================
    m,d=X.shape   #m为样本数，d为特征数
    m,l=Y.shape   #l为输出向量维数
    #===============初始化参数===============
    if para0==None:
        V=np.random.randn(d,q)     # “输入层-隐藏层”连接权重, 产生正则分布的随机数
        W=np.random.randn(q,l)     # “隐藏层-输出层”连接权重
        gama=np.random.randn(1,q)  # 隐藏层阈值
        theta=np.random.randn(1,l) #输出层阈值
    else:
        # 这里要注意，之所以如此繁琐的copy操作，是因为若不如此，
        # V,W,gama,theta各参数的变化也会引起输入变量para0的变化
        V0,W0,gama0,theta0=para0
        V=V0.copy()
        W=W0.copy()
        gama=gama0.copy()
        theta=theta0.copy()
    #==============训练神经网络==============
    erro=[]
    for i in range(iteration):
        #--------前向传播--------
        b=sigmoid(np.dot(X,V)-np.dot(np.ones([m,1]),gama))      # 隐藏层输出
        Ypre=sigmoid(np.dot(b,W)-np.dot(np.ones([m,1]),theta))  # 输出层输出
        #--------后向传播--------
        g=Ypre*(1-Ypre)*(Y-Ypre)
        e=b*(1-b)*(np.dot(g,W.T))
        #--------更新参数--------
        W+=alpha*np.dot(b.T,g)/m
        V+=alpha*np.dot(X.T,e)/m
        theta-=alpha*np.dot(np.ones([1,m]),g)/m
        gama-=alpha*np.dot(np.ones([1,m]),e)/m
        #---计算当前累积均方误差--
        erro.append(E(X,Y,(V,W,gama,theta)))
    return (V,W,gama,theta),erro